单例模式
概述
顾名思义,也就是一个类只允许一个实例,多用于线程池,全局缓存等等
满足条件:1. 只有一个实例;2. 全局访问
但其实,js本身就提供了单例模式,比如就在全局 const instance = {}; 这便满足的单例的两个条件
所以传统的单例模式在 js 中是不适用的

实现
普通实现
const CreateInstance = (function () {
let _instance = null;
return function (name) {
if (_instance) return _instance;
this.name = name;
_instance = this;
};
})();
const instance = new CreateInstance("x-1");
const instance2 = new CreateInstance("x-2");
console.log(instance === instance2); // true
代理模式实现
上面的实现方式有一种缺点,就是万一我需要这个构造函数又能单例使用,也能正常使用,那就要写两个,而且构造函数高度重合,且这种方式实现阅读不友好,且违背了单一职责原则(创建和管理写在了一起),于是我们可以采用另一种方式实现:代理模式实现
正常创建实例的构造函数正常定义,而在需要让这个构造函数满足单例模式的时候,用代理去创建
const CreateInstance = function (name) {
this.name = name;
// 此处不管有多少内容,我始终只需要写一遍
};
const ProxyCreate = (function () {
let _instance = null;
return function (name) {
if (_instance) return _instance;
_instance = new CreateInstance(name);
return _instance;
};
})();
const instance = new ProxyCreate("x-1");
const instance2 = new ProxyCreate("x-2");
console.log(instance === instance2); // true
惰性单例
这是单例模式的重点
在使用的时候才去创建实例,且这个创建满足单例模式,即惰性单例
满足单一职责的情况下实现:
// 创建实例构造函数
const CreateDiv = function () {
const div = document.createElement("div");
return document.body.appendChild(div);
};
const CreateIframe = function () {
const div = document.createElement("iframe");
return document.body.appendChild(div);
};
// 管理实例
const getInstance = function (creator) {
let _instance = null;
return function () {
return _instance || (_instance = creator.apply(this, arguments));
};
};
// 操作实例
const createLoginLayer = getInstance(CreateDiv);
document.getElementById("button").addEventListener("click", () => {
const loginLayer = createLoginLayer();
loginLayer.className = "show";
});
策略模式
概述
定义一些列算法,将他们封装起来,并是他们可以互相替换
互相替换是对于静态语言而言,因为在传入不同策略的时候,这些策略的类型得保持一致,静态语言中用抽象接口实现,而 js 中仅仅只需要保证他们有同一目的即可
实现
比如现在要实现一个年终奖计算程序,对于不同的绩效等级,有着不同的计算方法,而这些方法就是一个个策略
const strategies = {
A: salary => salary * 3,
B: salary => salary * 2,
C: salary => salary * 1,
};
const getBonus = (level, salary) => {
return strategies[level](salary);
};
console.log(getBonus('A', 1000)) // 3000
console.log(getBonus('B', 2000)) // 4000
这里有多态得体现,对于 getBonus 函数而言,传入不同参数,执行不同策略,这就是多态的体现
策略模式实现小球不同缓动效果
<body>
<div style="position: absolute; background: blue; left: 0" id="ball">
我是 div
</div>
<button onclick="main('linear')" style="margin-top: 100px">linear</button>
<button onclick="main('easeIn')" style="margin-top: 100px">easeIn</button>
</body>
<script>
const main = (() => {
let _instance = null;
return type => {
const animate =
_instance || (_instance = new Animate(document.getElementById("ball")));
animate.start("left", type, 0, 400, 2000);
};
})();
// 算法
var strategies = {
linear: function (time, startPos, endPos, duration) {
return (endPos * time) / duration + startPos;
},
easeIn: function (time, startPos, endPos, duration) {
return endPos * (time /= duration) * time + startPos;
},
};
const Animate = function (dom) {
this.dom = dom;
this.startPos = 0;
this.endPos = 0;
this.duration = 0;
this.type = null; // 运动算法
this.time = null;
this.property = "";
};
// 开始
Animate.prototype.start = function (
property,
type,
startPos,
endPos,
duration
) {
this.property = property;
this.type = type;
this.startPos = startPos;
this.endPos = endPos;
this.duration = duration;
this.time = new Date().getTime();
// 箭头函数 this 已绑定域外
const timerId = setInterval(() => {
// 如果运动结束
if (this.time + this.duration <= new Date().getTime()) {
clearInterval(timerId);
this.update(this.endPos);
}
this.update();
}, 19);
};
// 更新位置
Animate.prototype.update = function (endPos) {
const newPos = endPos || this.step(this.type);
this.dom.style[this.property] = newPos + "px";
};
// 计算目前该在那个位置,使用策略
Animate.prototype.step = function (type) {
return strategies[type](
new Date().getTime() - this.time,
this.startPos,
this.endPos,
this.duration
);
};
</script>
在线预览:https://codepen.io/xzboss/pen/dyBzOgg
策略模式实现表单校验
我们经常使用的 el-plus 中的 form 表单的 rules 配置项就很好的体现了策略模式
比如:<font style="background-color:rgb(245, 247, 250);"> { required: true, message: 'age is required' }, { type: 'number', message: 'age must be a number' }</font>,这个数组就是这条输入框所需要验证的规则集,也就是策略。
下面进行简单实现:
<body>
<form action="" id="form">
<label for="username">
用户名:
<input type="text" id="username" />
</label>
<br />
<label for="password" placeholder="密码">
密码:
<input type="text" id="password" />
</label>
<br />
<p class="tip" style="color: red;"></p>
<label for="submit">
<input type="submit" value="submit" />
</label>
</form>
</body>
<script>
// 验证策略
const strategies = {
isEmpty: (value, errMsg) => {
if (!value) return errMsg;
},
minLength: (value, len = 6, errMsg) => {
if (value.length < 6) return errMsg;
},
pattern: (value, reg, errMsg) => {
if (!reg.test(value)) return errMsg;
},
};
// 定义验证器
const Validator = function () {
this.errMsgs = []; // 验证结果集合
this.cache = []; // 待进行的验证函数 [][]
};
Validator.prototype.add = function (value, rules) {
this.cache.push([]);
for (const rule of rules) {
const { strategy, errMsg } = rule;
const arg = strategy.split(":");
this.cache[this.cache.length - 1].push(() => {
const s = arg.shift();
return strategies[s](value, ...arg, errMsg);
});
}
};
Validator.prototype.start = function () {
// debugger
this.cache.forEach(validFns => {
for (const validFn of validFns) {
const errMsg = validFn();
if (errMsg) {
this.errMsgs.push(errMsg);
break; // 对于一个值只要有一条规则不满足,就退出这个值的剩余验证规则,因为此值的提示最多显示一条
}
}
});
};
// 提交进行验证
document.getElementById("form").onsubmit = () => {
const username = document.getElementById("username");
const password = document.getElementById("password");
const tip = document.getElementsByClassName("tip")[0];
const validator = new Validator();
validator.add(username.value, [
{ strategy: "isEmpty", errMsg: "用户名不能为空" },
]);
validator.add(password.value, [
{ strategy: "isEmpty", errMsg: "密码不能为空" },
{ strategy: "minLength:6", errMsg: "密码不能少于6位" },
]);
validator.start();
if (validator.errMsgs.length) {
tip.textContent = validator.errMsgs.join(",");
return false;
}
alert('通过验证')
};
</script>
在线预览:https://codepen.io/xzboss/pen/RwzZoOm
代理模式
概述
访问真实对象改为访问代理对象,由代理对象去访问真实对象
代理对象和真实对象对外必须保持一致
代理分两种模式
- 保护代理:用于鉴权是否有权限访问真实对象
- 虚拟代理:当需要用到资源的时候才去创建,而不是直接用真实对象创建(下面都是虚拟代理例子)
虚拟代理模式实现图片预加载
<style>
img {
width: 50px;
height: 50px;
}
</style>
<body></body>
<script>
// 创建图片并添加到 dom
const myImage = (function () {
const imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: src => {
imgNode.src = src;
},
};
})();
// 代理对象,负责预加载
const proxyImage = (function () {
const img = new Image();
img.onload = () => {
myImage.setSrc(img.src);
};
return {
setSrc: src => {
console.log(111);
myImage.setSrc("./loading.gif");
img.src = src;
},
};
})();
proxyImage.setSrc(
"https://ts1.cn.mm.bing.net/th?id=OIP-C.aBZtnPTfuqMAFSpyZg0-vQHaGH"
);
</script>
在线预览:https://codepen.io/xzboss/pen/gONxgNm
虚拟代理模式实现合并多请求
<body>
<input type="checkbox" id="1" />
<input type="checkbox" id="2" />
<input type="checkbox" id="3" />
<input type="checkbox" id="4" />
<input type="checkbox" id="5" />
<input type="checkbox" id="6" />
<input type="checkbox" id="7" />
<input type="checkbox" id="8" />
<script>
// 正常请求
const synchronousFile = id => {
console.log("同步请求-", id, "中");
};
// 代理请求
const proxyFile = (() => {
const cache = new Set(); // 记录需要同步的 id
let timer = null;
return id => {
if (cache.has(id)) {
cache.delete(id);
} else {
cache.add(id);
}
if (timer) return;
// 节流控制每两秒最多触发一次同步
timer = setTimeout(() => {
for (const id of cache) {
synchronousFile(id);
}
clearTimeout(timer);
timer = null;
}, 2000);
};
})();
// 绑定点击事件
Array.prototype.forEach.call(
document.getElementsByTagName("input"),
element => {
element.onclick = () => proxyFile(element.id);
}
);
</script>
</body>
虚拟代理实现微打印台
假设我们自定义了一个打印台,这个打印台我们要在按了 f2 后才真实渲染在的页面上;但是在没按 f2 之前,我们需要提前决定要打印哪些数据
这里第一反应是用一个缓存列表,缓存打印结果,然后在 miniConsole 中进行缓存,在加载完成后,读出缓存;显然这样的方式是错误的,因为需要在 miniConsole 中做处理,违反单一职责,而且如果我不需要看打印,这个miniConsole 任然进行了加载并执行了打印,费时费存储。
正确思路,当遇到页面中需要打印的时候,交给代理进行存储,存储需要打印数据,等用户真实用 F2 打开打印弹窗时,再去加载 js 并执行打印。
即惰性加载
- HTML 代码
<head></head>
<body>
<script>
// 定义代理对象
var miniConsole = (function () {
const cache = []; // 缓存待执行打印函数
document.addEventListener("keyup", e => {
if (e.key === "F2") {
// 加载真 miniConsole
const scriptNode = document.createElement("script");
document.head.appendChild(scriptNode);
scriptNode.src = "./miniConsole.js";
// 加载后执行堆积打印函数,此时 miniConsole 已被替换
scriptNode.onload = () => {
cache.forEach(fn => {
fn();
});
};
}
});
return {
log (value) {
cache.push(() => {
return miniConsole.log(value);
});
},
};
})();
for (let i = 0; i < 10; i++) {
miniConsole.log("message" + i);
}
</script>
</body>
- js 库
var miniConsole = (function () {
let modal = null;
const createModal = function () {
const divNode = document.createElement("div");
divNode.style.cssText =
"width:500px;height:500px;position:absolute;right:0;top:0;background:red;";
document.body.appendChild(divNode);
modal = divNode;
};
return {
log: value => {
if (!modal) createModal();
modal.insertAdjacentHTML('beforeend', value + '<br />');
// modal.style.display = "none";
},
};
})();
在线预览:https://codepen.io/xzboss/pen/jOjGjVr
迭代器模式
内部迭代器
迭代规则在内部已经决定了,无法在外部影响。比如 forEach,内部已经决定了顺序,依次的迭代,无法终止(当然可以通过抛出错误来终止,)
外部迭代器
外部需要显式的调用 next 方法来进行迭代,更加灵活,可以终止可以关闭
发布订阅模式(观察者模式)
由发布者、订阅者、事件中心组成
功能组成: 发布,订阅,取消订阅,延迟订阅,命名空间
命令模式